package org.sgmiller.quickstem.tests;

import org.sgmiller.quickstem.IsolationLevel;
import org.sgmiller.quickstem.SerializationException;
import org.sgmiller.quickstem.Stem;
import org.sgmiller.quickstem.Transaction;

public class Account {
	String accountHolder;
	final Stem<Double> balance = new Stem<Double>(0.0);

	public Account(String accountHolder) {
		super();
		this.accountHolder = accountHolder;
	}

	public double getBalance() {
		return balance.get();
	}

	public void setBalance(double balance) {
		this.balance.set(balance);
	}
	
	public void debit(double amount) {
		balance.set(balance.get() - amount);
	}

	public void credit(double amount) {
		balance.set(balance.get() + amount);
	}

	public static void transfer(Account from, Account to, double amount, boolean fail, boolean delay) {
		Transaction.begin();
		try {
			if (from.getBalance() < amount) {
				throw new RuntimeException("Not enough in account " + from);
			}
			from.debit(amount);
			// ... something may go wrong.... HERE
			if (fail) throw new RuntimeException("Deliberate failure");
			if (delay) {
				sleep(1000);				
			}

			to.credit(amount);
			Transaction.commit();
		} catch (RuntimeException rte) {
			rte.printStackTrace();
			// No problem, we just roll back, no one loses anything
			Transaction.rollback();
		}
	}

	public static void transferConsistently(Account from, Account to,
			double amount, boolean delay) {
		while (true) {
			Transaction.begin(IsolationLevel.SERIALIZABLE);
			try {
				if (from.getBalance() < amount) {
					throw new RuntimeException("Not enough in account " + from);
				}
				from.debit(amount);
				if (delay) {
					delay=false;
					sleep(1000);					
				}
				to.credit(amount);
				Transaction.commit();
				break;
			} catch (SerializationException se) {
				se.printStackTrace();
				System.err.println("Retrying transfer.");
				// This means that the values a thread read during its
				// computation
				// of the balances was overwritten during the transaction. We
				// must retry.
			} catch (RuntimeException rte) {
				rte.printStackTrace();
				Transaction.rollback();
				break;
			}
		}
	}

	public String toString() {
		return accountHolder + '=' + balance.get();
	}

	public static void main(String[] args) throws Exception {
		Thread c1, c2;
		final Account a = new Account("Alice");
		final Account b = new Account("Bob");
		a.credit(100.0);
		b.credit(200.0);

		comment("See the initial balances");
		System.out.println(a);
		System.out.println(b);
		comment("Try a transfer, which fails half way through");
		Account.transfer(a,b,50,true,false);
		comment("See the same initial balances");
		System.out.println(a);
		System.out.println(b);
		
		comment("Try a transfer which succeeds");
		Account.transfer(a,b,50,false,false);
		comment("See the correct ending balances");
		System.out.println(a);
		System.out.println(b);
		
		
		comment("Try a concurrent transfer which will fail"); 
		c1=new Thread() {
			public void run() {
				Account.transfer(b,a,25,false,true);
			}
		};
		c1.start();

		c2=new Thread() {
			public void run() {
				Account.transfer(b,a,25,false,true);
			}
		};
		c2.start();
		
		c1.join();
		c2.join();

		comment("Now see the bad balance, it *should* be back to the initial balance, but it isn't.");
		System.out.println(a);
		System.out.println(b);
	
		comment("Let's reset things.");
		a.setBalance(50);
		b.setBalance(250);
		System.out.println(a);
		System.out.println(b);

		comment("And repeat the last one, but with serializable transactions.");
		c1=new Thread() {
			public void run() {
				Account.transferConsistently(b,a,25,true);
			}
		};
		c1.start();

		c2=new Thread() {
			public void run() {
				Account.transferConsistently(b,a,25,true);
			}
		};
		c2.start();
		
		c1.join();
		c2.join();
		comment("See the correct ending balances");
		System.out.println(a);
		System.out.println(b);
	}
	
	static void comment(String message) {
		sleep(500);
		System.err.println(message);
		sleep(1500);

	}
	static void sleep(int ms) {
		try {
			Thread.sleep(ms);
		} catch (InterruptedException e) {
		}
	}
}